package org.lab.mtga.core.model;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang.StringUtils;
import org.lab.mtga.core.exception.DeckViolation;
import org.lab.mtga.core.exception.InvalidDeckMode;

public class Deck {

	/** Nombre del deck */
	private String name;

	/** Lista de cartas actuales del deck */
	private List<Card> cardList;

	/** Distribucion de las cartas del mazo principal */
	private final Map<Card, Integer> mainCards;

	/** Distribucion de cartas del sideboard */
	private final Map<Card, Integer> sideboardCards;

	/** Variaciones del mazo, por ejemplo configuraciones de sideboard contra diferentes matchups */
	private final Map<String, Map<Card, Integer>> modes;

	public Deck() {
		cardList = new ArrayList<Card>();
		mainCards = new LinkedHashMap<Card, Integer>();
		sideboardCards = new LinkedHashMap<Card, Integer>();
		modes = new LinkedHashMap<String, Map<Card, Integer>>();
	}

	public void addCard(Card card, int count) {
		addCardToMap(card, count, mainCards);
	}

	public void addCardToSideboard(Card card, int count) {
		addCardToMap(card, count, sideboardCards);
	}

	public void addCardToOptionGroup(String mode, Card card, Integer count) {
		if (!modes.containsKey(mode)) {
			modes.put(mode, new LinkedHashMap<Card, Integer>());
		}
		Map<Card, Integer> map = modes.get(mode);
		map.put(card, count);
	}

	public void initialize() {
		initialize(null);
	}

	public void initialize(String name) {
		Map<Card, Integer> deckConfiguration = new LinkedHashMap<>();
		for (Card card : mainCards.keySet()) {
			deckConfiguration.put(card, mainCards.get(card));
		}
		if (StringUtils.isNotBlank(name)) {
			if (!modes.containsKey(name)) {
				throw new InvalidDeckMode(name);
			}
			Map<Card, Integer> mode = modes.get(name);
			for (Card card : mode.keySet()) {
				if (deckConfiguration.containsKey(card)) {
					int oldValue = deckConfiguration.get(card);
					int newValue = oldValue + mode.get(card);
					deckConfiguration.put(card, newValue);
				} else {
					deckConfiguration.put(card, mode.get(card));
				}
			}
		}
		cardList.clear();
		for (Card card : deckConfiguration.keySet()) {
			int count = deckConfiguration.get(card);
			for (int i = 0; i < count; i++) {
				cardList.add(card);
			}
		}
	}

	public List<Card> getCardList() {
		if (cardList.isEmpty()) {
			throw new RuntimeException("Deck is not initialized");
		}
		return cardList;
	}

	public int getCardCount(CardType... filters) {
		int count = 0;
		for(Card card : cardList) {
			boolean matches = false;
			for(CardType filter : filters) {
				if(card.matchesType(filter)) {
					matches = true;
					break;
				}
			}
			if(matches) {
				count++;
			}
		}
		return count;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Map<Card, Integer> getMainCards() {
		return mainCards;
	}

	public Map<Card, Integer> getSideboard() {
		return sideboardCards;
	}

	public Map<String, Map<Card, Integer>> getModes() {
		return modes;
	}

	private void addCardToMap(Card card, Integer count, Map<Card, Integer> map) {
		Boolean isBasicLand = card.matchesType(CardType.BasicLand);
		Integer current = map.containsKey(card) ? map.get(card) : 0;
		if (!isBasicLand && (current + count) > 4) {
			throw new DeckViolation("Invalid card count '" + card.getName() + "': " + (current + count));
		}
		if (map.containsKey(card)) {
			map.put(card, current + count);
		} else {
			map.put(card, count);
		}
	}
}
